001    /*
002     * Copyright 2004-2005 Stephen McConnell
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.transit.tools;
020    
021    import java.io.IOException;
022    import java.io.InputStream;
023    
024    import java.net.URI;
025    import java.util.Map;
026    import java.util.Hashtable;
027    
028    import java.util.List;
029    import java.util.ArrayList;
030    
031    import net.dpml.util.ElementHelper;
032    
033    import net.dpml.lang.Part;
034    import net.dpml.lang.Resource;
035    
036    import org.apache.tools.ant.BuildException;
037    import org.apache.tools.ant.BuildListener;
038    import org.apache.tools.ant.Project;
039    import org.apache.tools.ant.ComponentHelper;
040    
041    import org.w3c.dom.Element;
042    
043    /**
044     * The plugin task handles the establishment of ant tasks, listeners, and antlibs derived
045     * from a classloader established by the transit sub-system.
046     *
047     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
048     * @version 1.0.1
049     */
050    public class PluginTask extends TransitTask
051    {    
052        private Map m_map = new Hashtable();
053    
054       /**
055        * The uri of the plugin to load.
056        */
057        private String m_uri;
058    
059       /**
060        * Overloaded plugin urn.
061        */
062        private String m_urn;
063    
064       /**
065        * A list of tasks declared by the plugin declaration.
066        */
067        private List m_tasks = new ArrayList();
068    
069       /**
070        * List of listeners declared by the plugin task declaration.
071        */
072        private List m_listeners = new ArrayList();
073    
074       /**
075        * List of antlibs declared by the plugin task declaration.
076        */
077        private List m_antlibs = new ArrayList();
078    
079       /**
080        * Plugin name delalred by the plugin task declaration.
081        */
082        private String m_name;
083    
084       /**
085        * A flag indicating that nested directives have been provided.
086        */
087        private boolean m_flag = false;
088    
089       /**
090        * Set the project.
091        * @param project the current project
092        */
093        public void setProject( Project project )
094        {
095            setTaskName( "plugin" );
096            super.setProject( project );
097        }
098    
099       /**
100        * Create and associate a new antlib urn entry with the plugin.
101        * @return the new antlib entry
102        */
103        public Antlib createAntlib()
104        {
105            m_flag = true;
106            final Antlib antlib = new Antlib();
107            m_antlibs.add( antlib );
108            return antlib;
109        }
110    
111       /**
112        * Create and associate a new task entry with the plugin.
113        * @return the new task entry
114        */
115        public Task createTask()
116        {
117            m_flag = true;
118            final Task task = new Task();
119            m_tasks.add( task );
120            return task;
121        }
122    
123       /**
124        * Create and associate a new build listener with the plugin.
125        * @return the new listener entry
126        */
127        public Listener createListener()
128        {
129            m_flag = true;
130            final Listener listener = new Listener();
131            m_listeners.add( listener );
132            return listener;
133        }
134    
135       /**
136        * Set the artifact uri of the plugin from which the task is to be loaded.
137        * @param uri an artifact plugin uri
138        */
139        public void setUri( String uri )
140        {
141            m_uri = uri;
142        }
143    
144       /**
145        * Overload the urn to assign to the plugin.
146        * @param urn the urn to use
147        */
148        public void setUrn( String urn )
149        {
150            m_urn = urn;
151        }
152    
153       /**
154        * Return the artifact uri of the plugin.
155        * @return the plugin uri
156        */
157        public URI getUri()
158        {
159            try
160            {
161                return new URI( m_uri );
162            }
163            catch( Throwable e )
164            {
165                final String error =
166                  "Cound not convert the supplied uri spec ["
167                  + m_uri
168                  + "] to a formal URI.";
169                throw new BuildException( error, e, getLocation() );
170            }
171        }
172    
173       /**
174        * Load the plugin and handle registration of listeners, tasks, and antlib
175        * declarations based on the nested nested task directives.
176        * @exception BuildException if an error occurs during plugin loading or deployment
177        */
178        public void execute() throws BuildException
179        {
180            if( null == m_uri )
181            {
182                final String error =
183                  "Missing uri attribute.";
184                throw new BuildException( error );
185            }
186    
187            final Project project = getProject();
188            final ComponentHelper helper =
189              ComponentHelper.getComponentHelper( project );
190    
191            if( !m_flag )
192            {
193                createAntlib();
194            }
195    
196            try
197            {
198                URI uri = new URI( m_uri );
199                Part part = getPart( uri );
200                
201                ClassLoader loader = part.getClassLoader();
202                Task[] tasks = (Task[]) m_tasks.toArray( new Task[0] );
203                if( tasks.length > 0 )
204                {
205                    for( int i=0; i < tasks.length; i++ )
206                    {
207                        try
208                        {
209                            Task task = tasks[i];
210                            String classname = task.getClassname();
211                            String name = task.getName();
212                            Class c = loader.loadClass( classname );
213                            helper.addTaskDefinition( name, c );
214                        }
215                        catch( Throwable e )
216                        {
217                            final String error =
218                              "Failed to load a named task ["
219                              + tasks[i].getName()
220                              + "] from the plugin ["
221                              + uri
222                              + "].";
223                            throw new BuildException( error, e, getLocation() );
224                        }
225                    }
226                }
227    
228                Listener[] listeners = (Listener[]) m_listeners.toArray( new Listener[0] );
229                for( int i=0; i < listeners.length; i++ )
230                {
231                    Listener listener = listeners[i];
232                    String classname = listener.getClassname();
233                    Class c = loader.loadClass( classname );
234                    Object object = c.newInstance();
235                    if( object instanceof BuildListener )
236                    {
237                        BuildListener instance = (BuildListener) object;
238                        getProject().addBuildListener( instance );
239                        log( "registered listener: " + instance.getClass().getName() );
240                    }
241                    else
242                    {
243                        final String error =
244                          "The plugin ["
245                          + uri
246                          + "] establishing the class ["
247                          + object.getClass().getName()
248                          + "] could not be registered as a project listener because it does not implement the ["
249                          + BuildListener.class.getName()
250                          + "] interface.";
251                        throw new BuildException( error, getLocation() );
252                    }
253                }
254    
255                Antlib[] antlibs = (Antlib[]) m_antlibs.toArray( new Antlib[0] );
256                for( int i=0; i < antlibs.length; i++ )
257                {
258                    loadAntlib( uri, loader, helper, antlibs[i] );
259                }
260            }
261            catch( BuildException e )
262            {
263                throw e;
264            }
265            catch( Throwable e )
266            {
267                final String error = "Unable to load the plugin ["
268                  + m_uri
269                  + "] due to "
270                  + e.toString();
271                throw new BuildException( error, e, getLocation() );
272            }
273        }
274        
275        private Part getPart( URI uri ) throws IOException
276        {
277            return Part.load( uri, true );
278        }
279    
280       /**
281        * Load an antlib.
282        * @param classloader the classloader from which the antlib will be loaded
283        * @param helper the component helper
284        * @param antlib the antlib to load
285        * @exception Exception if it doesn't work out
286        */
287        private void loadAntlib(
288          URI uri, ClassLoader classloader, ComponentHelper helper, Antlib antlib ) throws Exception
289        {
290            Part part = getPart( uri );
291            
292            String resource = antlib.getPath();
293            if( null == resource )
294            {
295                if( part instanceof Resource )
296                {
297                    Resource res = (Resource) part;
298                    resource = res.getPath();
299                }
300            }
301            if( null == resource )
302            {
303                final String error =
304                  "Resource path for the antlib is not declared in the plugin descriptor "
305                  + "or antlib directive ["
306                  + uri
307                  + "]";
308                throw new BuildException( error, getLocation() );
309            }
310            
311            String urn = getAntLibURN( antlib, part );
312            if( null == urn )
313            {
314                final String error =
315                  "URN for the antlib is not declared in the plugin descriptor "
316                  + "or antlib directive ["
317                  + uri
318                  + "]";
319                throw new BuildException( error, getLocation() );
320            }
321    
322            InputStream input = classloader.getResourceAsStream( resource );
323            Element root = ElementHelper.getRootElement( input );
324            Element[] tasks = ElementHelper.getChildren( root, "taskdef" );
325            for( int i=0; i < tasks.length; i++ )
326            {
327                Element task = tasks[i];
328                String name = ElementHelper.getAttribute( task, "name" );
329                String classname = ElementHelper.getAttribute( task, "classname" );
330                loadTaskDef( classloader, helper, classname, urn + ":" + name );
331            }
332    
333            Element[] types = ElementHelper.getChildren( root, "typedef" );
334            for( int i=0; i < types.length; i++ )
335            {
336                Element type = types[i];
337                String name = ElementHelper.getAttribute( type, "name" );
338                String classname = ElementHelper.getAttribute( type, "classname" );
339                loadTypeDef( classloader, helper, classname, urn + ":" + name );
340            }
341        }
342        
343        private String getAntLibURN( Antlib antlib, Part part )
344        {
345            if( null != m_urn )
346            {
347                return m_urn;
348            }
349            String urn = antlib.getURN();
350            if( null != urn )
351            {
352                return urn;
353            }
354            else
355            {
356                if( part instanceof Resource )
357                {
358                    Resource res = (Resource) part;
359                    return res.getURN();
360                }
361                else
362                {
363                    return null;
364                }
365            }
366        }
367    
368       /**
369        * Load a single task defintion.
370        * @param loader the classloader from which the task will be loaded
371        * @param helper the component helper
372        * @param classname the task classname
373        * @param name the task name
374        * @exception BuildException if an error occurs while attempting to load the task
375        */
376        private void loadTaskDef( ClassLoader loader, ComponentHelper helper, String classname, String name )
377          throws BuildException
378        {
379            if( getProject().getTaskDefinitions().get( name ) != null )
380            {
381                return;
382            }
383    
384            try
385            {
386                Class c = loader.loadClass( classname );
387                helper.addTaskDefinition( name, c );
388                log( "installed taskdef: " + name, Project.MSG_VERBOSE );
389            }
390            catch( BuildException e )
391            {
392                throw e;
393            }
394            catch( Throwable e )
395            {
396                 final String error =
397                   "Unable to load task [" 
398                   + name 
399                   + "] from class [" 
400                   + classname
401                   + "].";
402                 throw new BuildException( error, e, getLocation() );
403            }
404        }
405    
406       /**
407        * Load a single type defintion.
408        * @param loader the classloader from which the type will be loaded
409        * @param helper the component helper
410        * @param classname the type classname
411        * @param name the task type
412        * @exception BuildException if an error occurs while attempting to load the task
413        */
414        private void loadTypeDef( ClassLoader loader, ComponentHelper helper, String classname, String name )
415          throws BuildException
416        {
417            if( getProject().getDataTypeDefinitions().get( name ) != null )
418            {
419                return;
420            }
421    
422            try
423            {
424                Class c = loader.loadClass( classname );
425                helper.addDataTypeDefinition( name, c );
426                log( "installed typedef: " + name, Project.MSG_VERBOSE );
427            }
428            catch( BuildException e )
429            {
430                throw e;
431            }
432            catch( Throwable e )
433            {
434                 final String error =
435                   "Unable to load type [" 
436                   + name + "] from class [" 
437                   + classname
438                   + "].";
439                 throw new BuildException( error, e, getLocation() );
440            }
441        }
442    
443       /**
444        * Nested element with the <plugin> element declaring the name and class of
445        * a task to be loaded from the classloader established by the transit plugin descriptor.
446        */
447        public static class Task
448        {
449           /**
450            * The task name.
451            */
452            private String m_name;
453    
454           /**
455            * The task classname.
456            */
457            private String m_classname;
458    
459           /**
460            * Set the task name.
461            * @param name the name of the task
462            */
463            public void setName( final String name )
464            {
465                m_name = name;
466            }
467    
468           /**
469            * Set the task classname.
470            * @param classname the task classname
471            */
472            public void setClass( final String classname )
473            {
474                m_classname = classname;
475            }
476    
477           /**
478            * Return the task classname.
479            * @return the classname
480            * @exception BuildException if the class attribute is missing
481            */
482            public String getClassname() throws BuildException
483            {
484                if( null == m_classname )
485                {
486                    final String error =
487                      "Missing class attribute.";
488                    throw new BuildException( error );
489                }
490                return m_classname;
491            }
492    
493           /**
494            * Return the task name.
495            * @return the name
496            * @exception BuildException if the name attribute is missing
497            */
498            public String getName() throws BuildException
499            {
500                if( null == m_name )
501                {
502                    final String error =
503                      "Missing name attribute.";
504                    throw new BuildException( error );
505                }
506                return m_name;
507            }
508        }
509    
510       /**
511        * Nested element with the <plugin> element declaring the name and class of
512        * a project listener to be loaded from the classloader established by the transit plugin descriptor.
513        */
514        public static class Listener
515        {
516           /**
517            * The listener classname.
518            */
519            private String m_classname;
520    
521           /**
522            * Set the task classname.
523            * @param classname the task classname
524            */
525            public void setClass( final String classname )
526            {
527                m_classname = classname;
528            }
529    
530           /**
531            * Return the task classname.
532            * @return the classname
533            * @exception BuildException if the class attribute is missing
534            */
535            public String getClassname() throws BuildException
536            {
537                if( null == m_classname )
538                {
539                    final String error =
540                      "Missing class attribute.";
541                    throw new BuildException( error );
542                }
543                return m_classname;
544            }
545        }
546    
547       /**
548        * Nested element with the <plugin> element declaring a packaged resource and urn of
549        * an antlib descriptor to be loaded from the classloader established by the transit plugin
550        * descriptor.
551        */
552        public static class Antlib
553        {
554           /**
555            * The antlib urn.
556            */
557            private String m_urn;
558    
559           /**
560            * The antlib descriptor resource path.
561            */
562            private String m_path;
563    
564           /**
565            * Set the urn for this antlib.
566            * @param urn the antlib urn
567            */
568            public void setUrn( final String urn )
569            {
570                m_urn = urn;
571            }
572    
573           /**
574            * Return the antlib urn.
575            * @return the urn (possibly null in which case the a urn must be declared
576            *   within the plugin descriptor)0
577            */
578            public String getURN()
579            {
580                return m_urn;
581            }
582    
583           /**
584            * Set the antlib resource path
585            * @param path the resource path
586            */
587            public void setResource( final String path )
588            {
589                m_path = path;
590            }
591    
592           /**
593            * Return the antlib resource path.
594            * @return the path (possibly null in which case the resource reference
595            *   must exist in the plugin descriptor)
596            */
597            public String getPath()
598            {
599                return m_path;
600            }
601        }
602    }